{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using backend: pytorch\n"
     ]
    }
   ],
   "source": [
    "import dgl.nn as dglnn\n",
    "from dgl import from_networkx\n",
    "import torch.nn as nn\n",
    "import torch as th\n",
    "import torch.nn.functional as F\n",
    "import dgl.function as fn\n",
    "from dgl.data.utils import load_graphs\n",
    "import networkx as nx\n",
    "import pandas as pd\n",
    "import socket\n",
    "import struct\n",
    "import random\n",
    "from sklearn.preprocessing import LabelEncoder\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "from sklearn.model_selection import train_test_split\n",
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py:3062: DtypeWarning: Columns (7,9) have mixed types. Specify dtype option on import or set low_memory=False.\n",
      "  has_raised = await self.run_ast_nodes(code_ast.body, cell_name,\n"
     ]
    }
   ],
   "source": [
    "data = pd.read_csv('./bot.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.drop(columns=['subcategory','pkSeqID','stime','flgs','attack','state','proto','seq'],inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.rename(columns={\"category\": \"label\"},inplace = True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "DDoS              1926624\n",
       "DoS               1650260\n",
       "Reconnaissance      91082\n",
       "Normal                477\n",
       "Theft                  79\n",
       "Name: label, dtype: int64"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data.label.value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "le = LabelEncoder()\n",
    "le.fit_transform(data.label.values)\n",
    "data['label'] = le.transform(data['label'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['saddr'] = data.saddr.apply(str)\n",
    "data['sport'] = data.sport.apply(str)\n",
    "data['daddr'] = data.daddr.apply(str)\n",
    "data['dport'] = data.dport.apply(str)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['saddr'] = data.saddr.apply(lambda x: socket.inet_ntoa(struct.pack('>I', random.randint(0xac100001, 0xac1f0001))))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['saddr'] = data['saddr'] + ':' + data['sport']\n",
    "data['daddr'] = data['daddr'] + ':' + data['dport']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.drop(columns=['sport','dport'],inplace=True)\n",
    "label_ground_truth = data[[\"saddr\", \"daddr\",\"label\"]]\n",
    "data = pd.get_dummies(data, columns = ['flgs_number','state_number', 'proto_number'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = data.reset_index()\n",
    "data.replace([np.inf, -np.inf], np.nan,inplace = True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.fillna(0,inplace = True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "label_ground_truth = data[[\"saddr\", \"daddr\",\"label\"]]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.drop(columns=['index'],inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>saddr</th>\n",
       "      <th>daddr</th>\n",
       "      <th>pkts</th>\n",
       "      <th>bytes</th>\n",
       "      <th>ltime</th>\n",
       "      <th>dur</th>\n",
       "      <th>mean</th>\n",
       "      <th>stddev</th>\n",
       "      <th>sum</th>\n",
       "      <th>min</th>\n",
       "      <th>...</th>\n",
       "      <th>state_number_7</th>\n",
       "      <th>state_number_8</th>\n",
       "      <th>state_number_9</th>\n",
       "      <th>state_number_10</th>\n",
       "      <th>state_number_11</th>\n",
       "      <th>proto_number_1</th>\n",
       "      <th>proto_number_2</th>\n",
       "      <th>proto_number_3</th>\n",
       "      <th>proto_number_4</th>\n",
       "      <th>proto_number_5</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>172.27.155.152:49960</td>\n",
       "      <td>192.168.100.7:80</td>\n",
       "      <td>8</td>\n",
       "      <td>1980</td>\n",
       "      <td>1.528089e+09</td>\n",
       "      <td>7.056393</td>\n",
       "      <td>0.068909</td>\n",
       "      <td>0.068909</td>\n",
       "      <td>0.137818</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>172.22.192.185:-1</td>\n",
       "      <td>192.168.100.147:-1</td>\n",
       "      <td>2</td>\n",
       "      <td>120</td>\n",
       "      <td>1.528089e+09</td>\n",
       "      <td>0.000131</td>\n",
       "      <td>0.000131</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000131</td>\n",
       "      <td>0.000131</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>172.30.47.65:49962</td>\n",
       "      <td>192.168.100.7:80</td>\n",
       "      <td>8</td>\n",
       "      <td>2126</td>\n",
       "      <td>1.528089e+09</td>\n",
       "      <td>7.047852</td>\n",
       "      <td>0.064494</td>\n",
       "      <td>0.064494</td>\n",
       "      <td>0.128988</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>172.20.20.121:49964</td>\n",
       "      <td>192.168.100.7:80</td>\n",
       "      <td>8</td>\n",
       "      <td>2024</td>\n",
       "      <td>1.528089e+09</td>\n",
       "      <td>7.047592</td>\n",
       "      <td>0.064189</td>\n",
       "      <td>0.064189</td>\n",
       "      <td>0.128378</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>172.28.237.223:49966</td>\n",
       "      <td>192.168.100.7:80</td>\n",
       "      <td>8</td>\n",
       "      <td>2319</td>\n",
       "      <td>1.528089e+09</td>\n",
       "      <td>7.046841</td>\n",
       "      <td>0.063887</td>\n",
       "      <td>0.063887</td>\n",
       "      <td>0.127774</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3668517</th>\n",
       "      <td>172.19.12.210:35064</td>\n",
       "      <td>192.168.100.3:22</td>\n",
       "      <td>6</td>\n",
       "      <td>434</td>\n",
       "      <td>1.529381e+09</td>\n",
       "      <td>0.013165</td>\n",
       "      <td>0.013165</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.013165</td>\n",
       "      <td>0.013165</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3668518</th>\n",
       "      <td>172.26.30.200:35066</td>\n",
       "      <td>192.168.100.3:22</td>\n",
       "      <td>6</td>\n",
       "      <td>434</td>\n",
       "      <td>1.529381e+09</td>\n",
       "      <td>0.000574</td>\n",
       "      <td>0.000574</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000574</td>\n",
       "      <td>0.000574</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3668519</th>\n",
       "      <td>172.24.181.134:35070</td>\n",
       "      <td>192.168.100.3:22</td>\n",
       "      <td>31</td>\n",
       "      <td>5472</td>\n",
       "      <td>1.529381e+09</td>\n",
       "      <td>2.874302</td>\n",
       "      <td>2.874302</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>2.874302</td>\n",
       "      <td>2.874302</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3668520</th>\n",
       "      <td>172.27.134.47:43001</td>\n",
       "      <td>192.168.100.150:4433</td>\n",
       "      <td>2</td>\n",
       "      <td>134</td>\n",
       "      <td>1.529381e+09</td>\n",
       "      <td>0.000003</td>\n",
       "      <td>0.000003</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000003</td>\n",
       "      <td>0.000003</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3668521</th>\n",
       "      <td>172.25.233.121:-1</td>\n",
       "      <td>192.168.100.149:-1</td>\n",
       "      <td>16</td>\n",
       "      <td>960</td>\n",
       "      <td>1.529382e+09</td>\n",
       "      <td>848.002686</td>\n",
       "      <td>0.000145</td>\n",
       "      <td>0.000056</td>\n",
       "      <td>0.001160</td>\n",
       "      <td>0.000080</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>3668522 rows × 58 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                        saddr                 daddr  pkts  bytes  \\\n",
       "0        172.27.155.152:49960      192.168.100.7:80     8   1980   \n",
       "1           172.22.192.185:-1    192.168.100.147:-1     2    120   \n",
       "2          172.30.47.65:49962      192.168.100.7:80     8   2126   \n",
       "3         172.20.20.121:49964      192.168.100.7:80     8   2024   \n",
       "4        172.28.237.223:49966      192.168.100.7:80     8   2319   \n",
       "...                       ...                   ...   ...    ...   \n",
       "3668517   172.19.12.210:35064      192.168.100.3:22     6    434   \n",
       "3668518   172.26.30.200:35066      192.168.100.3:22     6    434   \n",
       "3668519  172.24.181.134:35070      192.168.100.3:22    31   5472   \n",
       "3668520   172.27.134.47:43001  192.168.100.150:4433     2    134   \n",
       "3668521     172.25.233.121:-1    192.168.100.149:-1    16    960   \n",
       "\n",
       "                ltime         dur      mean    stddev       sum       min  \\\n",
       "0        1.528089e+09    7.056393  0.068909  0.068909  0.137818  0.000000   \n",
       "1        1.528089e+09    0.000131  0.000131  0.000000  0.000131  0.000131   \n",
       "2        1.528089e+09    7.047852  0.064494  0.064494  0.128988  0.000000   \n",
       "3        1.528089e+09    7.047592  0.064189  0.064189  0.128378  0.000000   \n",
       "4        1.528089e+09    7.046841  0.063887  0.063887  0.127774  0.000000   \n",
       "...               ...         ...       ...       ...       ...       ...   \n",
       "3668517  1.529381e+09    0.013165  0.013165  0.000000  0.013165  0.013165   \n",
       "3668518  1.529381e+09    0.000574  0.000574  0.000000  0.000574  0.000574   \n",
       "3668519  1.529381e+09    2.874302  2.874302  0.000000  2.874302  2.874302   \n",
       "3668520  1.529381e+09    0.000003  0.000003  0.000000  0.000003  0.000003   \n",
       "3668521  1.529382e+09  848.002686  0.000145  0.000056  0.001160  0.000080   \n",
       "\n",
       "         ...  state_number_7  state_number_8  state_number_9  state_number_10  \\\n",
       "0        ...               0               0               0                0   \n",
       "1        ...               0               0               0                0   \n",
       "2        ...               0               0               0                0   \n",
       "3        ...               0               0               0                0   \n",
       "4        ...               0               0               0                0   \n",
       "...      ...             ...             ...             ...              ...   \n",
       "3668517  ...               0               0               0                0   \n",
       "3668518  ...               0               0               0                0   \n",
       "3668519  ...               0               0               0                0   \n",
       "3668520  ...               0               0               0                0   \n",
       "3668521  ...               0               0               0                0   \n",
       "\n",
       "         state_number_11  proto_number_1  proto_number_2  proto_number_3  \\\n",
       "0                      0               1               0               0   \n",
       "1                      0               0               1               0   \n",
       "2                      0               1               0               0   \n",
       "3                      0               1               0               0   \n",
       "4                      0               1               0               0   \n",
       "...                  ...             ...             ...             ...   \n",
       "3668517                0               1               0               0   \n",
       "3668518                0               1               0               0   \n",
       "3668519                0               1               0               0   \n",
       "3668520                0               1               0               0   \n",
       "3668521                0               0               1               0   \n",
       "\n",
       "         proto_number_4  proto_number_5  \n",
       "0                     0               0  \n",
       "1                     0               0  \n",
       "2                     0               0  \n",
       "3                     0               0  \n",
       "4                     0               0  \n",
       "...                 ...             ...  \n",
       "3668517               0               0  \n",
       "3668518               0               0  \n",
       "3668519               0               0  \n",
       "3668520               0               0  \n",
       "3668521               0               0  \n",
       "\n",
       "[3668522 rows x 58 columns]"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "scaler = StandardScaler()\n",
    "cols_to_norm = list(set(list(data.iloc[:, 2:].columns ))  - set(list(['label'])) )\n",
    "data[cols_to_norm] = scaler.fit_transform(data[cols_to_norm])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "     data, label_ground_truth, test_size=0.3, random_state=42, stratify=label_ground_truth.label)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "<ipython-input-18-841b8327893c>:1: SettingWithCopyWarning: \n",
      "A value is trying to be set on a copy of a slice from a DataFrame.\n",
      "Try using .loc[row_indexer,col_indexer] = value instead\n",
      "\n",
      "See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
      "  X_train['h'] = X_train[ cols_to_norm ].values.tolist()\n"
     ]
    }
   ],
   "source": [
    "X_train['h'] = X_train[ cols_to_norm ].values.tolist()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "#from dgl.data.utils import load_graphs   \n",
    "#G = load_graphs(\"./data.bin\")[0][0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "G = nx.from_pandas_edgelist(X_train, \"saddr\", \"daddr\", ['h','label'], create_using= nx.MultiGraph())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "G = G.to_directed()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "G = from_networkx(G,edge_attrs=['h','label'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "#from dgl.data.utils import save_graphs\n",
    "#save_graphs(\"./data.bin\", [G])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "G.ndata['h'] = th.ones(G.num_nodes(), G.edata['h'].shape[1]) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "G.edata['train_mask'] = th.ones(len(G.edata['h']), dtype= th.bool)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "#G = load_graphs(\"./bot_train_G.bin\") [0][0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Eq1\n",
    "G.ndata['h'] = th.ones(G.num_nodes(), G.edata['h'].shape[1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "G.edata['train_mask'] = th.ones(len(G.edata['h']), dtype=th.bool)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "G.ndata['h'] = th.reshape(G.ndata['h'], (G.ndata['h'].shape[0], 1, G.ndata['h'].shape[1]))\n",
    "G.edata['h'] = th.reshape(G.edata['h'], (G.edata['h'].shape[0], 1, G.edata['h'].shape[1]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MLPPredictor(nn.Module):\n",
    "    def __init__(self, in_features, out_classes):\n",
    "        super().__init__()\n",
    "        self.W = nn.Linear(in_features * 2, out_classes)\n",
    "\n",
    "    def apply_edges(self, edges):\n",
    "        h_u = edges.src['h']\n",
    "        h_v = edges.dst['h']\n",
    "        score = self.W(th.cat([h_u, h_v], 1))\n",
    "        return {'score': score}\n",
    "\n",
    "    def forward(self, graph, h):\n",
    "        with graph.local_scope():\n",
    "            graph.ndata['h'] = h\n",
    "            graph.apply_edges(self.apply_edges)\n",
    "            return graph.edata['score']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([2580999, 1, 55])"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "G.ndata['h'].shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_accuracy(pred, labels):\n",
    "    return (pred.argmax(1) == labels).float().mean().item()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SAGELayer(nn.Module):\n",
    "    def __init__(self, ndim_in, edims, ndim_out, activation):\n",
    "        super(SAGELayer, self).__init__()\n",
    "        ### force to outut fix dimensions\n",
    "        self.W_msg = nn.Linear(ndim_in + edims, ndim_out)\n",
    "        ### apply weight\n",
    "        self.W_apply = nn.Linear(ndim_in + ndim_out, ndim_out)\n",
    "        self.activation = activation\n",
    "\n",
    "    def message_func(self, edges):\n",
    "        return {'m': self.W_msg(th.cat([edges.src['h'], edges.data['h']], 2))}\n",
    "\n",
    "    def forward(self, g_dgl, nfeats, efeats):\n",
    "        with g_dgl.local_scope():\n",
    "            g = g_dgl\n",
    "            g.ndata['h'] = nfeats\n",
    "            g.edata['h'] = efeats\n",
    "            # Eq4\n",
    "            g.update_all(self.message_func, fn.mean('m', 'h_neigh'))\n",
    "            # Eq5          \n",
    "            g.ndata['h'] = F.relu(self.W_apply(th.cat([g.ndata['h'], g.ndata['h_neigh']], 2)))\n",
    "            return g.ndata['h']\n",
    "\n",
    "\n",
    "class SAGE(nn.Module):\n",
    "    def __init__(self, ndim_in, ndim_out, edim, activation, dropout):\n",
    "        super(SAGE, self).__init__()\n",
    "        self.layers = nn.ModuleList()\n",
    "        self.layers.append(SAGELayer(ndim_in, edim, 128, activation))\n",
    "        self.layers.append(SAGELayer(128, edim, ndim_out, activation))\n",
    "        self.dropout = nn.Dropout(p=dropout)\n",
    "\n",
    "    def forward(self, g, nfeats, efeats):\n",
    "        for i, layer in enumerate(self.layers):\n",
    "            if i != 0:\n",
    "                nfeats = self.dropout(nfeats)\n",
    "            nfeats = layer(g, nfeats, efeats)\n",
    "        return nfeats.sum(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Model(nn.Module):\n",
    "    def __init__(self, ndim_in, ndim_out, edim, activation, dropout):\n",
    "        super().__init__()\n",
    "        self.gnn = SAGE(ndim_in, ndim_out, edim, activation, dropout)\n",
    "        self.pred = MLPPredictor(ndim_out, 5)\n",
    "    def forward(self, g, nfeats, efeats):\n",
    "        h = self.gnn(g, nfeats, efeats)\n",
    "        return self.pred(g, h)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.utils import class_weight\n",
    "class_weights = class_weight.compute_class_weight('balanced',\n",
    "                                                 np.unique(G.edata['label'].cpu().numpy()),\n",
    "                                                 G.edata['label'].cpu().numpy())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "class_weights = th.FloatTensor(class_weights).cuda()\n",
    "criterion = nn.CrossEntropyLoss(weight = class_weights)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "G = G.to('cuda:0')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "device(type='cuda', index=0)"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "G.device"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "device(type='cuda', index=0)"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "G.ndata['h'].device  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "device(type='cuda', index=0)"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "G.edata['h'].device  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# node_features = G.ndata['h']\n",
    "# edge_features = G.edata['h']\n",
    "\n",
    "# edge_label = G.edata['label']\n",
    "# train_mask = G.edata['train_mask']\n",
    "\n",
    "# model = Model(G.ndata['h'].shape[2], 128, G.ndata['h'].shape[2], F.relu, 0.2).cuda()\n",
    "\n",
    "# opt = th.optim.Adam(model.parameters())\n",
    "\n",
    "# for epoch in range(1,14500):\n",
    "#     pred = model(G, node_features,edge_features).cuda()\n",
    "#     loss = criterion(pred[train_mask] ,edge_label[train_mask])\n",
    "#     opt.zero_grad()\n",
    "#     loss.backward()\n",
    "#     opt.step()\n",
    "#     if epoch % 100 == 0:\n",
    "#         print('Epoch:', epoch ,' Training acc:', compute_accuracy(pred[train_mask], edge_label[train_mask]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "<ipython-input-42-84be06ca8888>:1: SettingWithCopyWarning: \n",
      "A value is trying to be set on a copy of a slice from a DataFrame.\n",
      "Try using .loc[row_indexer,col_indexer] = value instead\n",
      "\n",
      "See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
      "  X_test['h'] = X_test[ cols_to_norm ].values.tolist()\n"
     ]
    }
   ],
   "source": [
    "X_test['h'] = X_test[ cols_to_norm ].values.tolist()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "#G_test = load_graphs(\"bot_test_G.bin\") [0][0]\n",
    "G_test = nx.from_pandas_edgelist(X_test, \"saddr\", \"daddr\", ['h','label'],create_using=nx.MultiGraph())\n",
    "G_test = G_test.to_directed()\n",
    "G_test = from_networkx(G_test,edge_attrs=['h','label'] )\n",
    "actual = G_test.edata.pop('label')\n",
    "G_test.ndata['feature'] = th.ones(G_test.num_nodes(), 55)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "G_test.ndata['feature'] = th.reshape(G_test.ndata['feature'], (G_test.ndata['feature'].shape[0], 1, G_test.ndata['feature'].shape[1]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "G_test.edata['h'] = th.reshape(G_test.edata['h'], (G_test.edata['h'].shape[0], 1, G_test.edata['h'].shape[1]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [],
   "source": [
    "G_test = G_test.to('cuda:0')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "th.cuda.empty_cache()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "import timeit\n",
    "start_time = timeit.default_timer()\n",
    "node_features_test = G_test.ndata['feature']\n",
    "edge_features_test = G_test.edata['h']\n",
    "test_pred = model(G_test, node_features_test, edge_features_test).cuda()\n",
    "elapsed = timeit.default_timer() - start_time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.5602441180017195 seconds\n"
     ]
    }
   ],
   "source": [
    "print(str(elapsed) + ' seconds')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_pred = test_pred.argmax(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_pred = th.Tensor.cpu(test_pred).detach().numpy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "edge_label = le.inverse_transform(actual)\n",
    "test_pred = le.inverse_transform(test_pred)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_confusion_matrix(cm,\n",
    "                          target_names,\n",
    "                          title='Confusion matrix',\n",
    "                          cmap=None,\n",
    "                          normalize=True):\n",
    "    \n",
    "    import matplotlib.pyplot as plt\n",
    "    import numpy as np\n",
    "    import itertools\n",
    "\n",
    "    accuracy = np.trace(cm) / float(np.sum(cm))\n",
    "    misclass = 1 - accuracy\n",
    "\n",
    "    if cmap is None:\n",
    "        cmap = plt.get_cmap('Blues')\n",
    "\n",
    "    plt.figure(figsize=(12, 12))\n",
    "    plt.imshow(cm, interpolation='nearest', cmap=cmap)\n",
    "    plt.title(title)\n",
    "    plt.colorbar()\n",
    "\n",
    "    if target_names is not None:\n",
    "        tick_marks = np.arange(len(target_names))\n",
    "        plt.xticks(tick_marks, target_names, rotation=45)\n",
    "        plt.yticks(tick_marks, target_names)\n",
    "\n",
    "    if normalize:\n",
    "        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]\n",
    "\n",
    "\n",
    "    thresh = cm.max() / 1.5 if normalize else cm.max() / 2\n",
    "    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n",
    "        if normalize:\n",
    "            plt.text(j, i, \"{:0.4f}\".format(cm[i, j]),\n",
    "                     horizontalalignment=\"center\",\n",
    "                     color=\"white\" if cm[i, j] > thresh else \"black\")\n",
    "        else:\n",
    "            plt.text(j, i, \"{:,}\".format(cm[i, j]),\n",
    "                     horizontalalignment=\"center\",\n",
    "                     color=\"white\" if cm[i, j] > thresh else \"black\")\n",
    "\n",
    "\n",
    "    plt.tight_layout()\n",
    "    plt.ylabel('True label')\n",
    "    plt.xlabel('Predicted label\\naccuracy={:0.4f}; misclass={:0.4f}'.format(accuracy, misclass))\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 864x864 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from sklearn.metrics import confusion_matrix\n",
    "\n",
    "plot_confusion_matrix(cm = confusion_matrix(edge_label, test_pred), \n",
    "                      normalize    = False,\n",
    "                      target_names = np.unique(edge_label),\n",
    "                      title        = \"Confusion Matrix\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
